/**
 * $version:  0.1 
 * $Date: 2011-01-21 
 *
 * Copyright (C) 2010-2011 Jawa Software. All rights reserved.
 *
 */
package org.jawa.core.database;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.jawa.core.util.JawaConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 用于生成在数据库中使用的序列id.
 */
public class SequenceManager {

	private static final Logger Log = LoggerFactory.getLogger(SequenceManager.class);

	// 序列生成器的类型
	private int type;
	// 序列生成器当前值
	private long currentID;
	// 序列生成器最大值
	private long maxID;
	// 序列生成器每次分配空间
	private int blockSize;

	private static final String CREATE_ID = "INSERT INTO app_id (id, idtype) VALUES (1, ?)";

	private static final String LOAD_ID = "SELECT id FROM app_id WHERE idtype=?";

	private static final String UPDATE_ID = "UPDATE app_id SET id=? WHERE idtype=? AND id=?";

	// 用于管理每个一个序列生成器.
	private static Map<Integer, SequenceManager> managers = new ConcurrentHashMap<Integer, SequenceManager>();

	static {
		new SequenceManager(JawaConstants.DB_USER_SEQ, 5);
	}

	/**
	 * 创建一个数据库支持的序列生成器.
	 * 
	 * @param seqType
	 *            序列生成器的类型.
	 * @param blockSize
	 *            每次从数据库取出的序列数量.
	 */
	public SequenceManager(int seqType, int blockSize) {
		managers.put(seqType, this);
		this.type = seqType;
		this.blockSize = blockSize;
		currentID = 0l;
		maxID = 0l;
	}

	/**
	 * 返回指定类型的下一个序列.
	 * 
	 * @param type
	 *            下一个序列生成器的类型.
	 * @return 下一个序列.
	 */
	public static long nextID(int type) {
		if (managers.containsKey(type)) {
			return managers.get(type).nextUniqueID();
		} else {
			SequenceManager manager = new SequenceManager(type, 1);
			return manager.nextUniqueID();
		}
	}

	/**
	 * 将指定类型的序列生成器每次数据库取出序列值.如果指定的序列生成器不存在，则创建一个新的.
	 * 
	 * @param type
	 *            序列生成器的类型.
	 * @param blockSize
	 *            每次从数据库取出的序列数量.
	 */
	public static void setBlockSize(int type, int blockSize) {
		if (managers.containsKey(type)) {
			managers.get(type).blockSize = blockSize;
		} else {
			new SequenceManager(type, blockSize);
		}
	}

	/**
	 * 返回下一个可用的唯一序列ID.
	 */
	public synchronized long nextUniqueID() {
		if (!(currentID < maxID)) {
			// 获取下一个序列值范围.
			getNextBlock(5);
		}
		long id = currentID;
		currentID++;
		return id;
	}

	/**
	 * 执行查找来获得下一个可用的ID序列范围块.算法如下:
	 * <ol>
	 * <li>选中当前序列的数据库行.
	 * <li>返回数据库的增加序列范围信息.
	 * <li>将数据库的中序列值改为新的序列值 where id=old_id.
	 * <li>如果更新失败，继续回到第一步进行尝试(共尝试 count 次).
	 * </ol>
	 * 
	 * @param count
	 *            数据库失败后尝试次数.
	 */
	private void getNextBlock(int count) {
		if (count == 0) {
			Log.error("获取下一个序更失败, 放弃...");
			return;
		}

		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		boolean abortTransaction = false;
		boolean success = false;

		try {
			con = DbConnectionManager.getTransactionConnection();
			// 获取当前序列生成器信息.
			pstmt = con.prepareStatement(LOAD_ID);
			pstmt.setInt(1, type);
			rs = pstmt.executeQuery();

			long currentID = 1;
			if (rs.next()) {
				currentID = rs.getLong(1);
			} else {
				createNewID(con, type);
			}
			DbConnectionManager.fastcloseStmt(rs, pstmt);

			// 递增序列范围.
			long newID = currentID + blockSize;
			pstmt = con.prepareStatement(UPDATE_ID);
			pstmt.setLong(1, newID);
			pstmt.setInt(2, type);
			pstmt.setLong(3, currentID);
			// 检查是否操作成功，如果没有操作成功，代表其它线程已经改变了原来的序列值。因此本次操作失败，进入下一次尝试.
			success = pstmt.executeUpdate() == 1;
			if (success) {
				this.currentID = currentID;
				this.maxID = newID;
			}
		} catch (SQLException e) {
			Log.error(e.getMessage(), e);
			abortTransaction = true;
		} finally {
			DbConnectionManager.closeStatement(rs, pstmt);
			DbConnectionManager.closeTransactionConnection(con, abortTransaction);
		}

		if (!success) {
			Log.error("获取序列失败 " + type + ". 再次尝试...");
			// 再次调用此方法，通过睡眠尽量避免简单的线程挣抢
			try {
				Thread.sleep(75);
			} catch (InterruptedException ie) {
				// 忽略.
			}
			getNextBlock(count - 1);
		}
	}

	private void createNewID(Connection con, int type) throws SQLException {
		Log.warn("创建序列生成器 :'" + type + "'");

		PreparedStatement pstmt = null;
		try {
			pstmt = con.prepareStatement(CREATE_ID);
			pstmt.setInt(1, type);
			pstmt.execute();
		} finally {
			DbConnectionManager.closeStatement(pstmt);
		}
	}
}